<?PHP
/**
 * OpenStation
 *
 * @Application : Db.class.php
 * @Encode : 65001 Unicode ( UTF-8 NO BOM )
 * @Description : 数据驱动中间层
 * @Author : HK <hkyyang@gmail.com>
 */

class Db {

    // 数据库类型
    protected $dbType;
    // 启动永久连接
    protected $pconnect = FALSE;
    // 当前SQL语句
    protected $queryStr = NULL;
    // 执行返回的结果
    protected $queryID = NULL;
    // 最后插入的ID
    protected $lastInsertID = NULL;
    // 操作影响行数
    protected $numRows = 0;
    // 返回字段数
    protected $numCols = 0;
    // 事务数量
    protected $transNums = 0;
    // 错误信息
    protected $error = NULL;
    // 数据库连接ID
    protected $linkID = array();
    // 当前连接
    protected $link = NULL;
    // 执行时间记录
    protected $beginTime = NULL;
    // 已连接标记
    protected $connected = FALSE;
    // 数据库名
    public $dbName = NULL;
    // 数据表后缀
    public $tablePrefix = '';
    // 数据表前缀
    public $tableSuffix = '';
    // 是否启动数据表字段缓存
    public $fieldsCache = FALSE;
    // 是否启动数据表字段类型检查
    public $fieldsCheck = FALSE;
    // 数据库连接配置
    protected $config;
    // 连接失败重试次数
    protected $RetryNum = 5;
    // 已经连接失败次数
    protected $FailNum = 0;
    // 重试等待时间
    protected $RetryWait = 3;

    // 数据库表达式
    protected $comparison = array( 'eq' => '=', 'neq' => '!=', 'gt' => '>', 'egt' => '>=', 'lt' => '<', 'elt' => '<=', 'notlike' => 'NOT LIKE', 'like' => 'LIKE' );
    // SELECT 模板
    const __TPL_SELECT__ = 'SELECT%DISTINCT% %FIELDS% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%%LIMIT%';
    // DELETE 模板
    const __TPL_DELETE__ = 'DELETE FROM %TABLE%%WHERE%%ORDER%%LIMIT%';
    // UPDATE 模板
    const __TPL_UPDATE__ = 'UPDATE %TABLE% SET %SET%%WHERE%%ORDER%%LIMIT%';
    // WRITE 模板
    const __TPL_WRITE__ = '%WRITE% INTO %TABLE% %VALUES%';

    /**
     *  initial 方法，初始化
     *
     * @access protected
     * @param
     * @return void
     */
    protected function initial() {
        $this -> tablePrefix = $this -> config[ 'prefix' ];
        $this -> tableSuffix = $this -> config[ 'suffix' ];
        $this -> fieldsCache = $this -> config[ 'fields_cache' ] ? TRUE : FALSE;
        $this -> fieldsCheck = $this -> config[ 'fields_check' ] ? TRUE : FALSE;
        $this -> dbName = $this -> config[ 'database' ];
    }

    /**
     *  load 方法，根据配置载入数据驱动
     *
     * @static
     * @access public
     * @param Array : $config, 数据库配置
     * @return Object
     */
    static public function load( $config = array() ) {
        $db_config = self :: parseConfig( $config );
        $db_type = ucwords( strtolower( $db_config[ 'type' ] ) );
        $db_class = $db_type;
        import( 'Lib.Db.Driver.' . $db_class );

        if( class_exists( $db_class, FALSE ) ) {
            $db = new $db_class( $db_config );
        } else {
            throw_exception( Cache :: lang( 'DB_DRIVER_NOT_EXIST' ) . ': "' . strtoupper( $db_class ) . '"' );
        }
        return $db;
    }

    /**
     *  parseConfig 方法，解析并验证数据库配置
     *
     * @static
     * @access public
     * @param Array : $config
     * @return Array
     */
    static public function parseConfig( $config = array() ) {
        // 如果没有传入配置，使用默认的数据库配置
        $config = empty( $config ) ? Cache :: get( '@.DB' ) : $config;
        if( empty( $config ) ) {
            throw_exception( Cache :: lang( 'DB_CONFIG_MISSING' ) );
        }
        $db_config = array();
        // 存入Cache中安全操作
        Cache :: set( 'DB_CONFIG_TEMP', $config );
        $db_config = array(
            'type' => Cache :: get( 'DB_CONFIG_TEMP.TYPE' ),
            'username' => Cache :: get( 'DB_CONFIG_TEMP.USER' ),
            'password' => Cache :: get( 'DB_CONFIG_TEMP.PWD' ),
            'host' => Cache :: get( 'DB_CONFIG_TEMP.HOST' ),
            'port' => Cache :: get( 'DB_CONFIG_TEMP.PORT' ),
            'database' => Cache :: get( 'DB_CONFIG_TEMP.NAME' ),
            'charset' => Cache :: get( 'DB_CONFIG_TEMP.CHARSET' ),
            'prefix' => Cache :: get( 'DB_CONFIG_TEMP.PREFIX' ),
            'suffix' => Cache :: get( 'DB_CONFIG_TEMP.SUFFIX' ),
            'fields_cache' => Cache :: get( 'DB_CONFIG_TEMP.FIELDS_CACHE' ),
            'fields_check' => Cache :: get( 'DB_CONFIG_TEMP.FIELDS_CHECK' ),
        );

        // 解析DSN 配置
        if( Cache :: type( 'DB_CONFIG_TEMP.DSN', 'string' ) ) {
            $dsnConfig = self :: parseDSN( Cache :: get( 'DB_CONFIG_TEMP.DSN' ) );
            $db_config = array_merge( $db_config, $dsnConfig );
        }
        // 释放Cache临时变量
        Cache :: out( 'DB_CONFIG_TEMP' );
        // 检查必要配置
        foreach( $db_config as $k => $v ) {
            if( in_array( $k, array( 'type', 'username', 'password', 'host', 'database' ) ) && ( empty( $v ) || ! strlen( $v ) ) ) {
                throw_exception( Cache :: lang( 'DB_CONFIG_MISSING' ) . ': "' . $k . '"' );
                break;
            }
        }
        return $db_config;
    }

    /**
     * parseDSN 方法，DSN解析
     * 格式: mysql://username:passwd@localhost:3306/dbName
     *
     * @static
     * @access private
     * @param String : $dsnUrl
     * @return Array
     */
    static private function parseDSN( $dsnUrl ) {
        $info = parse_url( $dsnUrl );
        // 使用URL解析
        if( $info[ 'scheme' ] ) {
            // 存入Cache中安全操作
            Cache :: set( 'DB_DSN_TEMP', $info );
            $dsn = array(
                'type' => Cache :: get( 'DB_DSN_TEMP.SCHEME' ),
                'username' => Cache :: get( 'DB_DSN_TEMP.USER' ),
                'password' => Cache :: get( 'DB_DSN_TEMP.PASS' ),
                'host' => Cache :: get( 'DB_DSN_TEMP.HOST' ),
                'port' => Cache :: get( 'DB_DSN_TEMP.PORT' ),
                'database'   => string_before_remove( Cache :: get( 'DB_DSN_TEMP.SCHEME' ), '/' )
            );
            // 释放临时变量
            Cache :: out( 'DB_DSN_TEMP' );
        // 使用正则表达式匹配
        } else {
            preg_match( '/^(.*?)\:\/\/(.*?)\:(.*?)\@(.*?)\:([0-9]{1, 6})\/(.*?)$/', trim( $dsnUrl ), $matches );
            $dsn = array (
                'type' => $matches[ 1 ],
                'username' => $matches[ 2 ],
                'password' => $matches[ 3 ],
                'host' => $matches[ 4 ],
                'port' => $matches[ 5 ],
                'database' => $matches[ 6 ]
            );
        }
        return $dsn;
    }

    /**
     *  select 方法，查询记录
     *
     * @access public
     * @param Array : $options, 参数数组
     * @return *
     */
     public function select( $options ) {
        $sql = str_replace(
            array( '%TABLE%', '%DISTINCT%', '%FIELDS%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%' ),
            array(
                $this -> parseTable( $options ),
                $this -> parseDistinct( $options ),
                $this -> parseField( $options ),
                $this -> parseJoin( $options ),
                $this -> parseWhere( $options ),
                $this -> parseGroup( $options ),
                $this -> parseHaving( $options ),
                $this -> parseOrder( $options ),
                $this -> parseLimit( $options )
            ),
            self :: __TPL_SELECT__
        );
        $sql .= $this -> parseLock( isset( $options[ 'lock' ] ) ? $options[ 'lock' ] : FALSE );
        return $this -> query( $sql );
     }

    /**
     *  delete 方法，删除记录
     *
     * @access public
     * @param Array : $options, 参数数组
     * @return *
     */
     public function delete( $options ) {
        $sql = str_replace(
            array( '%TABLE%', '%WHERE%', '%ORDER%', '%LIMIT%' ),
            array(
                $this -> parseTable( $options ),
                $this -> parseWhere( $options ),
                $this -> parseOrder( $options ),
                $this -> parseLimit( $options )
            ),
            self :: __TPL_DELETE__
        );
        $sql .= $this -> parseLock( isset( $options[ 'lock' ] ) ? $options[ 'lock' ] : FALSE );
        return $this -> execute( $sql );
     }

    /**
     *  update 方法，更新记录
     *
     * @access public
     * @param Array : $data, 更新的数据
     * @param Array : $options, 参数数组
     * @return *
     */
    public function update( $data, $options ) {
        $sql = str_replace(
            array( '%TABLE%', '%SET%', '%WHERE%', '%ORDER%', '%LIMIT%' ),
            array(
                $this -> parseTable( $options ),
                $this -> parseSet( $data ),
                $this -> parseWhere( $options ),
                $this -> parseOrder( $options ),
                $this -> parseLimit( $options )
            ),
            self :: __TPL_UPDATE__
        );
        $sql .= $this -> parseLock( isset( $options[ 'lock' ] ) ? $options[ 'lock' ] : FALSE );
        return $this -> execute( $sql );
    }

    /**
     *  write 方法，写入记录
     *
     * @access public
     * @param Array : $data, 写入的数据
     * @param Array : $options, 参数数组
     * @return *
     */
    public function write( $data, $options ) {
        $sql = str_replace(
            array( '%WRITE%', '%TABLE%', '%VALUES%' ),
            array(
                $this -> parseWrite( $options ),
                $this -> parseTable( $options ),
                $this -> parseWriteValues( $data )
            ),
            self :: __TPL_WRITE__
        );
        $sql .= $this -> parseLock( isset( $options[ 'lock' ] ) ? $options[ 'lock' ] : FALSE );
        return $this -> execute( $sql );
    }

    /**
     *  initConnect 方法，初始化检查数据库连接
     *
     * @access protected
     * @return Array
     */
    protected function initConnect() {
        if( ! $this -> connected ) {
            $this -> link = $this -> connect();
        }
    }

    /**
     *  addSpecialChar 方法，字段表名添加`号(Mysql)
     *
     * @access public
     * @param String : & $value
     * @return String
     */
    public function addSpecialChar( & $value ) {
        if( $this -> dbType == 'MYSQL' ) {
            if( FALSE !== strpos( $value, ' ' ) || FALSE !== strpos( $value, ',' ) || FALSE !== strpos( $value, '*' ) || FALSE !== strpos( $value, '(' ) || FALSE !== strpos( $value, '`' ) ) {
                return $value;
            } else {
                if( strpos( $value, '.' ) ) {
                    $temp = explode( '.', $value );
                    array_walk( $temp, array( $this, 'addSpecialChar' ) );
                    $value = implode( '.', $temp );
                } else {
                    $value = '`' . $value . '`';
                }
            }
        }
        return $value;
    }

    /**
     * parseTable 方法，table解析
     *
     * @access protected
     * @param Array : $options
     * @return String
     */
    protected function parseTable( $options ) {
        $tables = $options[ 'table' ];
        if( is_string( $tables ) && strlen( $tables ) ) {
            $tables = explode( ',', $tables );
        }
        array_walk( $tables, array( & $this, 'addSpecialChar' ) );
        foreach( $tables as $k => $v ) {
            if( is_string( $k ) ) {
                $tables[ $k ] = $this -> addSpecialChar( $v ) . ' AS ' . $this -> addSpecialChar( $k );
            } else {
                $tables[ $k ] = $this -> addSpecialChar( $v );
            }
        }
        return implode( ', ', $tables );
     }

    /**
     * parseWrite 方法，根据不同的方法写入数据
     *
     * @access protected
     * @param Array : $options
     * @return String
     */
    protected function parseWrite( $options ) {
        if( isset( $options[ 'replace' ] ) ) {
            if( $options[ 'replace' ] ) {
                return 'REPLACE';
            }
        }
        return 'INSERT';
    }

    /**
     *  parseSet 方法，解析UPDATE SET数据
     *
     * @access protected
     * @param Array : $data
     * @return String
     */
    protected function parseSet( $data, $type = '' ) {
        $values = array();
        foreach( $data as $k => $v ) {
            $v = $this -> parseValue( $v );
            // 过滤非标量
            if( is_scalar( $v ) ) {
                $values[] = $this -> addSpecialChar( $k ) . ' = ' . $v;
            }
        }
        return implode( ',', $values );
    }

    /**
     *  parseWriteValues 方法，解析写入的数据
     *
     * @access protected
     * @param Array : $data
     * @return String
     */
    protected function parseWriteValues( $data ) {
        $values = array();
        foreach( $data as $k => $v ) {
            $v = $this -> parseValue( $v );
            // 过滤非标量
            if( is_scalar( $v ) ) {
                $values[ $this -> addSpecialChar( $k ) ] = $v;
            }
        }
        $fields = array_keys( $values );
        return ' ( ' . implode( ', ', $fields ) . ' ) VALUES ( ' . implode( ', ', $values ) . ' )';
    }

    /**
     *  parseDistinct 方法，distinct解析
     *
     * @access protected
     * @param Array : $options
     * @return String
     */
     protected function parseDistinct( $options ) {
        if( isset( $options[ 'distinct' ] ) && ! empty( $options[ 'distinct' ] ) ) {
            return ' DISTINCT ';
        }
        return '';
     }

    /**
     *  parseField 方法，field解析
     *
     * @access protected
     * @param Array : $options
     * @return String
     */
     protected function parseField( $options ) {
        if( isset( $options[ 'field' ] ) ) {
            if( is_string( $options[ 'field' ] ) && strlen( $options[ 'field' ] ) ) {
                $fields = explode( ',', $options[ 'field' ] );
            } else {
                $fields = $options[ 'field' ];
            }
            array_walk( $fields, 'trim' );
            $temp = array();
            foreach( $fields as $k => $v ) {
                if( is_numeric( $k ) ) {
                    $temp[] = $this -> addSpecialChar( $v );
                } else {
                    $temp[] = $this -> addSpecialChar( $v ) . ' AS ' . $this -> addSpecialChar( $k );
                }
            }
            return implode( ', ', $temp );
        }
        return '*';
     }

    /**
     *  parseJoin 方法，join解析(默认LEFT JOIN)
     *
     * @access protected
     * @param Array : $options
     * @return String
     */
     protected function parseJoin( $options ) {
        $join = '';
        if( isset( $options[ 'join' ] ) && ! empty( $options[ 'join' ] ) && is_array( $options[ 'join' ] ) ) {
            foreach( $options[ 'join' ] as $k => $v ) {
                if( stripos( $v, 'JOIN' ) === FALSE ) {
                    $join .= ' LEFT JOIN ' . $v;
                } else {
                    $join .= ' ' . $v;
                }
            }
        }
        return $join;
     }

    /**
     *  parseWhere 方法，where解析
     *
     * @access protected
     * @param Array : $options
     * @return String
     */
    protected function parseWhere( $options ) {
        $where = '';
        if( ! isset( $options[ 'where' ] ) || empty( $options[ 'where' ] ) ) {
            return ' ';
        } else if( isset( $options[ 'where' ] ) && is_string( $options[ 'where' ] ) && stripos( trim( $options[ 'where' ] ), 'WHERE' ) == 0 ) {
                // 解析WHERE字符串
                $where = ' ' . trim( $options[ 'where' ] ) . ' ';
                // 解析[字段名][操作][值]
                $where = preg_replace( '/\s(\S+?[^>|<])\s*?(LIKE|NOT LIKE|REGEXP|NOT REGEXP|IS NULL|NOT NULL|=|!=)\s*?(\'.*?\'|\".*?\")\s/ies', "\$this -> parseWhereFieldValueCallback( '\$1', '\$2', '\$3' )", $where );
                $where = ' WHERE ' . trim( $where );
        }
        return $where;
    }

    /**
     *  parseGroup 方法，gruop解析
     *
     * @access protected
     * @param Array : $options
     * @return String
     */
    protected function parseGroup( $options ) {
        if( isset( $options[ 'group' ] ) && ! empty( $options[ 'group' ] ) ) {
            return ' GROUP BY ' . $options[ 'group' ];
        }
        return '';
    }

    /**
     *  parseHaving 方法，having解析
     *
     * @access protected
     * @param Array : $options
     * @return String
     */
    protected function parseHaving( $options ) {
        if( isset( $options[ 'group' ] ) && isset( $options[ 'having' ] ) && ! empty( $options[ 'group' ] ) && ! empty( $options[ 'having' ] ) ) {
            return ' HAVING ' . $options[ 'having' ];
        }
        return '';
    }

    /**
     *  parseOrder 方法，order解析
     *
     * @access protected
     * @param Array : $options
     * @return String
     */
    protected function parseOrder( $options ) {
        if( isset( $options[ 'order' ] ) && ! empty( $options[ 'order' ] ) ) {
            return ' ORDER BY ' . $options[ 'order' ];
        }
        return '';
    }

    /**
     *  parseLimit 方法，limit解析
     *
     * @access protected
     * @param Array : $options
     * @return String
     */
    protected function parseLimit( $options ) {
        $limitStr = '';
        // 分页计算
        if( isset( $options[ 'page' ] ) ) {
            $page = intval( $options[ 'page' ] ) ? intval( $options[ 'page' ] ) : 1;
        }
        if( isset( $options[ 'limit' ] ) && ! empty( $options[ 'limit' ] ) ) {
            $limit = $options[ 'limit' ];
        }
        if( isset( $page ) ) {
            if( ! isset( $limit ) ) {
                $limit = 20;
            } else {
                $limit = intval( $limit );
            }
            $offset = $limit * ( $page - 1 );
            $limitStr = ' LIMIT ' . $offset . ',' . $limit;
        } else if( isset( $options[ 'limit' ] ) && ! empty( $options[ 'limit' ] ) ) {
            $limitStr = ' LIMIT ' . $options[ 'limit' ];
        }
        return $limitStr;
    }

    /**
     *  parseLock 方法，锁机制
     *
     * @access protected
     * @param Array : $options
     * @return String
     */
    protected function parseLock( $lock = FALSE ) {
        if( ! $lock ) {
            return '';
        }
        if( 'ORACLE' == $this -> dbType ) {
            return ' FOR UPDATE NOWAIT ';
        }
        return ' FOR UPDATE ';
    }

    /**
     *  parseValue 方法，value解析
     *
     * @access protected
     * @param * : $value
     * @return *
     */
    protected function parseValue( $value ) {
        if( is_string( $value ) ) {
            $value = '\'' . $this -> escape_string( $value ) . '\'';
        // SQL 不会被当作字符串而作为SQL语句执行
        } else if( isset( $value[ 0 ] ) && is_string( $value[ 0 ] ) && strtoupper( $value[ 0 ] ) == 'SQL' ) {
            $value = $this -> escape_string( $value[ 1 ] );
        } else if( is_array( $value ) ) {
            $value = array_map( array( $this, 'parseValue' ), $value );
        } else if( is_null( $value ) ) {
            $value = 'null';
        }
        if( strlen( $value ) < 1 ) {
            $value = '\'\'';
        }
        return $value;
    }


    /**
     *  getDbError 方法，获取数据库错误
     *
     * @access public
     * @param $
     * @return String
     */
    public function getDbError() {
        return $this -> error;
    }

    /**
     *  getQuerySql 方法，获取最后执行的语句
     *
     * @access public
     * @param $
     * @return String
     */
    public function getQuerySql() {
        return $this -> queryStr;
    }

    /**
     * getLastInsertID 方法，获取最后一次插入的ID
     *
     * @access public
     * @return Integer
     */
    public function getLastInsertID() {
        return $this -> lastInsertID;
    }

    /**
     * getNumRows 方法，获取影响的行数
     *
     * @access public
     * @return Integer
     */
    public function getNumRows() {
        return $this -> numRows;
    }

    /**
     * close 方法，关闭数据库连接
     *
     * @access public
     * @return Boolean
     */
    public function closeDB() {
        return $this -> close();
    }

    /**
     *  getInstance 方法，获取数据库实例
     *
     * @static
     * @access public
     * @param Array | String : $config
     * @return Object
     */
    static public function getInstance( $config ) {
        $object = get_instance_of( __CLASS__ );
        return $object -> load( $config );
    }

    /**
     * parseWhereFieldValueCallback 回调方法，解析WHERE字符串中的字段名, 操作, 值
     *
     * @callback
     * @access private
     * @param String $field
     * @param String $oper
     * @param String $value
     * @return String
     */
    private function parseWhereFieldValueCallback( $field, $oper, $value ) {
        $result = '';
        foreach( explode( '.', $field ) as $v ) {
            $v = string_double_remove( trim( $v ), '`' );
            $v = $this -> addSpecialChar( $v );
            $result .= $v . '.';
        }
        // 值解析
        $quote = substr( $value, 0, 1 );
        $value = string_double_remove( $value, array( '\'', '"' ) );
        $value = str_replace( array( '\\\'', '\"', '\\\\' ), array( '\'', '"', '\\' ), $value );
        $value = $this -> escape_string( $value );
        if( in_array( $quote, array( '\'', '"' ) ) ) {
            $value = $quote . $value . $quote;
        } else {
            $value = '\'' . $value . '\'';
        }

        return ' ' . string_after_remove( $result, '.' ) . ' ' . strtoupper( $oper ) . ' ' . $value . ' ';
    }


}
?>